package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;
    @Override
    public PageResult search(RequestParams params) {
        try {
            // 准备request
            SearchRequest request = new SearchRequest("hotel");
            // 构建复合查询BooleanQuery
            buildBasicQuery(params, request);
            // 分页
            pageSearch(params, request);
            // 排序
            basicSort(params, request);
            // 地理位置排序
            locationSort(params, request);
            // 发起请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 解析响应
            PageResult pageResult = handleResponse(response);
            return pageResult;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 基本排序
     * @param params
     * @param request
     */
    private void basicSort(RequestParams params, SearchRequest request) {
        if (params.getSortBy().equals("score")) {
            request.source().sort("score", SortOrder.ASC);
        }
        if (params.getSortBy().equals("price")) {
            request.source().sort("price", SortOrder.DESC);
        }
    }

    /**
     * 地理位置排序
     * @param params
     * @param request
     */
    private void locationSort(RequestParams params, SearchRequest request) {
        String location = params.getLocation();
        if (location != null && ! location.equals("")) {
            request.source().sort(SortBuilders
                    .geoDistanceSort("location", new GeoPoint(location))
                    .order(SortOrder.ASC)
                    .unit(DistanceUnit.KILOMETERS)
            );
        }
    }

    /**
     * 分页
     * @param params
     * @param request
     */
    private void pageSearch(RequestParams params, SearchRequest request) {
        int page = params.getPage();
        int size = params.getSize();
        request.source().from((page - 1) * size).size(size);
    }

    /**
     * 查询
     * @param params
     * @param request
     */
    private void buildBasicQuery(RequestParams params, SearchRequest request) {
        // 原始查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //关键字搜索
        // 复合查询的must部分
        String key = params.getKey();
        if (key == null || "".equals(key)) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else  {
            boolQuery.must(QueryBuilders.matchQuery("all", key));
            // 设置关键字高亮
            request.source().highlighter(new HighlightBuilder()
                    .field("name")
                    .requireFieldMatch(false)
            );
        }
        // 复合查询的filter部分
        // 城市条件
        if (params.getCity() != null && ! params.getCity().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        // 品牌条件
        if (params.getBrand() != null && ! params.getBrand().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        // 星际条件
        if (params.getStarName() != null && ! params.getStarName().equals("")) {
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
        // 价格条件
        if (params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQuery.filter(QueryBuilders
                    .rangeQuery("price")
                    .gte(params.getMinPrice())
                    .lte(params.getMaxPrice()));
        }
        request.source().query(getFunctionScoreQuery(boolQuery));
    }

    /**
     * 算分控制
     * @param boolQuery
     * @return
     */
    private FunctionScoreQueryBuilder getFunctionScoreQuery(BoolQueryBuilder boolQuery) {
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                // 原始查询，相关性算法的查询
                boolQuery,
                // function score数组
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[] {
                        // function score过滤条件
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                // 如果查询文档中包含isAD且为true，则进行加权
                                QueryBuilders.termQuery("isAD", true),
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                }
        );
        return functionScoreQuery;
    }

    /**
     * 解析响应
     * @param response
     * @return
     */
    private PageResult handleResponse(SearchResponse response) {
            SearchHits hits = response.getHits();
            // 数据的总条数
            long value = hits.getTotalHits().value;
    //        System.out.println("一共搜索到" + value + "条数据");
            SearchHit[] hitsArr = hits.getHits();
            // 数据的集合
            List<HotelDoc> list = new ArrayList<>();
            for (SearchHit hit : hitsArr) {
                // 获取json
                String json = hit.getSourceAsString();
                // 获取距离信息
                HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
                // 处理高亮
                highlightStyle(hit, hotelDoc);
                try {
                    Object[] values = hit.getSortValues();
                    Object sortValue = values[0];
                    hotelDoc.setDistance(sortValue);
                } catch (Exception e) {
    //                System.out.println("无效定位");
                } finally {
                    list.add(hotelDoc);
                }
            }
            return new PageResult(value, list);
        }

    /**
     * 高亮处理
     * @param hit
     * @param hotelDoc
     */
    private void highlightStyle(SearchHit hit, HotelDoc hotelDoc) {
        // 处理高亮
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        if ( ! CollectionUtils.isEmpty(highlightFields)) {
            // 根据字段名拿到高亮结果
            HighlightField highlightField = highlightFields.get("name");
            if (highlightField != null) {
                // 获取高亮值
                String name = highlightField.getFragments()[0].toString();
                hotelDoc.setName(name);
            }
        }
    }
}
